package com.taotaojs.util.reflect;

import com.taotaojs.exception.MyInnerException;

import java.beans.PropertyDescriptor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author: TaoTaojs
 * @Date: 2019/4/28 14:56
 * @Description:
 * <h1>对象操作工具</h1>
 * <h2>简介</h2>
 * 对对象进行判断、整理等一系列操作
 * <h3>主要功能：</h3>
 * <ol>
 *     <li>支持对象与Map的互转</li>
 *     <li>判断对象是否为空</li>
 *     <li>获取两个不同对象中相同名称与类型的属性</li>
 *     <li>获取泛型实例</li>
 *     <li>以某属性为key将集合整理为Map对象的方法</li>
 *     <li>深克隆</li>
 * </ol>
 * @关联类:
 */
public class EntityUtil {

    private EntityUtil(){}

    /**
     * 对象转为Map结构
     * @param obj 具体对象
     * @return Map结构
     * @throws Exception
     */
    public static Map<String,Object> obj2Map(Object obj){
        Map<String,Object> map=new HashMap<>(2);
        PropertyDescriptor[] propertyDescriptors = ReflectUtil.getPropertyList(obj.getClass());
        for (PropertyDescriptor property : propertyDescriptors) {
            String key = property.getName();
            if (key.compareToIgnoreCase("class") == 0) {
                continue;
            }
            Method getter = property.getReadMethod();
            if(getter != null){
                map.put(key, ReflectUtil.invokeMethod(obj, getter));
            }
        }
        return map;
    }

    /**
     * Map映射为具体对象（推荐直接使用BeanMap类）
     * @param map 属性数据
     * @param clz 类类型
     * @return 具体对象
     * @throws Exception
     */
    public static Object map2Obj(Map<String,Object> map, Class<?> clz){
        if (map == null) {
            return null;
        }
        Object obj = getInstance(clz);
        PropertyDescriptor[] propertyDescriptors = ReflectUtil.getPropertyList(obj.getClass());
        for (PropertyDescriptor property : propertyDescriptors) {
            Method setter = property.getWriteMethod();
            if (setter != null) {
                ReflectUtil.invokeMethod(obj, setter, map.get(property.getName()));
            }
        }
        return obj;
    }


    /**
     * 对象添加属性
     * 使用反射获取对应属性的set方法
     * @param item
     * @param map [属性名:值]
     * @return
     * @throws Exception
     */
    public static <T, V> T addProperty(T item, Map<String, V> map){
        for (Map.Entry<String, V> stringObjectEntry : map.entrySet()) {
            String key = stringObjectEntry.getKey();
            ReflectUtil.invokeSet(item, key, stringObjectEntry.getValue());
        }
        return item;
    }

    /**
     * 判断对象是否为空
     * @param obj
     * @return
     */
    public static boolean isEmpty(Object obj){
        if(obj == null){
            return true;
        }
        for (Field f : obj.getClass().getDeclaredFields()) {
            //公有化属性
            f.setAccessible(true);
            //判断字段是否为空，并且对象属性中的基本都会转为对象类型来判断
            if (ReflectUtil.invokeGet(obj, f) != null) {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取两个对象中相同名称和类型的属性
     *
     * @param <T1>
     * @param <T2>
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     */
    protected static <T1, T2> Map<String,Class<?>> getFieldList(Class<T1> clazz1, Class<T2> clazz2) {
        //获取所有属性
        Field[] fields1 = clazz1.getDeclaredFields();
        Field[] fields2 = clazz2.getDeclaredFields();
        //创建Map容器
        Map<String,Class<?>> fieldMap = new HashMap<>(2);
        for (Field f1 : fields1) {
            for (Field f2 : fields2) {
                //判断两个字段是否名称相同而且类型相同
                if (f1.getName().equals(f2.getName())
                        && equalFieldsType(f1, f2)) {
                    fieldMap.put(f1.getName(),f1.getType());
                }
            }
        }
        return fieldMap;
    }

    /**
     * 获取一个泛型的实例
     *
     * @param clazz
     * @param <T>
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static <T> T getInstance(Class<T> clazz) {
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            throw new MyInnerException(e);
        }
    }

    /**
     * 判断两个字段的类型是否相同
     *
     * @param field1 复制源
     * @param field2 复制目标
     * @return
     */
    public static boolean equalFieldsType(Field field1, Field field2) {
        String fTypeName1 = field1.getType().getSimpleName();
        String fTypeName2 = field2.getType().getSimpleName();
        //1. 处理基本数据类型和包装类
        Map<String, String> map = new HashMap<>(8);
        map.put(int.class.getSimpleName(), Integer.class.getSimpleName());
        map.put(byte.class.getSimpleName(), Byte.class.getSimpleName());
        map.put(short.class.getSimpleName(), Short.class.getSimpleName());
        map.put(char.class.getSimpleName(), Character.class.getSimpleName());
        map.put(long.class.getSimpleName(), Long.class.getSimpleName());
        map.put(float.class.getSimpleName(), Float.class.getSimpleName());
        map.put(double.class.getSimpleName(), Double.class.getSimpleName());
        map.put(boolean.class.getSimpleName(), Boolean.class.getSimpleName());

        /**
         * 在涉及包装类的判断逻辑中，源数据不能是包装类
         * 因为包装类一旦为null，会引发异常
         */
        for (Map.Entry<String, String> stringStringEntry : map.entrySet()) {
            boolean flag =  stringStringEntry.getKey().equals(fTypeName2) && map.get(stringStringEntry.getKey()).equals(fTypeName1);
            boolean flag1 = stringStringEntry.getKey().equals(fTypeName1) && map.get(stringStringEntry.getKey()).equals(fTypeName2);
            if (flag1 || flag) {
                return true;
            }
        }
        //2. 类型相同
        return fTypeName1.equals(fTypeName2);
    }


    /**
     * 以某个属性名称为key，将集合整理为Map对象（默认属性名为id）
     * @param clazz
     * @param list
     * @param proName
     * @param <K>
     * @param <V>
     * @return
     */
    public static<K, V> Map<K, V> objList2Map(Class<V> clazz, List<V> list, String proName){
        Map<K,V> map = new HashMap<>(2);
        Method getIdMethod = ReflectUtil.getGetMethod(clazz, proName);
        for (V v : list) {
            try {
                map.put((K)getIdMethod.invoke(v), v);
            } catch (Exception e) {
                throw new MyInnerException(e);
            }
        }
        return map;
    }
    public static<T> Map<Integer, T> objList2Map(Class<T> clazz, List<T> list){
        return objList2Map(clazz, list, "id");
    }

    /**
     * 根据类类型和属性名称获取对应的Field对象
     * @param clazz
     * @param fieldName
     * @param <T>
     * @return
     */
    public static<T> Field getField(Class<T> clazz, String fieldName){
        try {
            return clazz.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            throw new MyInnerException(e);
        }
    }


    /**
     * 深克隆对象
     * @param orig
     * @return
     */
    public static Object clone(Object orig) {
        Object obj = null;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bos);
            out.writeObject(orig);
            out.flush();
            out.close();

            ObjectInputStream in = new ObjectInputStream(
                    new ByteArrayInputStream(bos.toByteArray()));
            obj = in.readObject();
        } catch (Exception e) {
            throw new MyInnerException(e);
        }
        return obj;
    }

}
